use super::*;

impl<'a> Stream<'a> {
	#[inline]
	pub fn read_at_offset<O: Offset>(&mut self, data: &'a [u8]) -> Option<&'a [u8]>
	where
		Option<O>: FromData,
	{
		data.get(self.read::<Option<O>>()??.to_usize()..)
	}
	#[inline]
	pub fn parse_at_offset<O: Offset, T: FromSlice<'a>>(&mut self, data: &'a [u8]) -> Option<T>
	where
		Option<O>: FromData,
	{
		self.read_at_offset(data).and_then(T::parse)
	}
	#[inline]
	pub fn read_at_offset16(&mut self, data: &'a [u8]) -> Option<&'a [u8]> {
		let offset = self.read::<Option<Offset16>>()??.to_usize();
		data.get(offset..)
	}
	#[inline]
	pub fn parse_at_offset16<T: FromSlice<'a>>(&mut self, data: &'a [u8]) -> Option<T> {
		self.read_at_offset16(data).and_then(T::parse)
	}
	#[inline]
	pub fn read_at_offset32(&mut self, data: &'a [u8]) -> Option<&'a [u8]> {
		let offset = self.read::<Option<Offset32>>()??.to_usize();
		data.get(offset..)
	}
	#[inline]
	pub fn parse_at_offset32<T: FromSlice<'a>>(&mut self, data: &'a [u8]) -> Option<T> {
		self.read_at_offset32(data).and_then(T::parse)
	}
}

/// A common offset methods.
pub trait Offset {
	/// Converts the offset to `usize`.
	fn to_usize(&self) -> usize;
}

#[derive(Clone, Copy)]
pub struct Offset8(pub u8);
impl Offset for Offset8 {
	#[inline]
	fn to_usize(&self) -> usize {
		usize::from(self.0)
	}
}
impl FromData for Offset8 {
	const SIZE: usize = 2;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		u8::parse(data).map(Offset8)
	}
}
impl FromData for Option<Offset8> {
	const SIZE: usize = Offset8::SIZE;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		let offset = Offset8::parse(data)?;
		if offset.0 != 0 {
			Some(Some(offset))
		} else {
			Some(None)
		}
	}
}
impl core::fmt::Debug for Offset8 {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "Offset8({})", self.0)
	}
}

/// A type-safe u16 offset.
#[derive(Clone, Copy)]
pub struct Offset16(pub u16);
impl Offset for Offset16 {
	#[inline]
	fn to_usize(&self) -> usize {
		usize::from(self.0)
	}
}
impl FromData for Offset16 {
	const SIZE: usize = 2;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		u16::parse(data).map(Offset16)
	}
}
impl FromData for Option<Offset16> {
	const SIZE: usize = Offset16::SIZE;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		let offset = Offset16::parse(data)?;
		if offset.0 != 0 {
			Some(Some(offset))
		} else {
			Some(None)
		}
	}
}
impl core::fmt::Debug for Offset16 {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "Offset16({})", self.0)
	}
}

/// A type-safe u24 offset.
#[derive(Clone, Copy)]
pub struct Offset24(pub u32);
impl Offset for Offset24 {
	#[inline]
	fn to_usize(&self) -> usize {
		self.0 as usize //	usize::num_from(self.0)
	}
}
impl FromData for Offset24 {
	const SIZE: usize = 3;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		U24::parse(data).map(|n| Offset24(n.0))
	}
}
impl FromData for Option<Offset24> {
	const SIZE: usize = Offset24::SIZE;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		let offset = Offset24::parse(data)?;
		if offset.0 != 0 {
			Some(Some(offset))
		} else {
			Some(None)
		}
	}
}
impl core::fmt::Debug for Offset24 {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "Offset24({})", self.0)
	}
}

/// A type-safe u32 offset.
#[derive(Clone, Copy)]
pub struct Offset32(pub u32);
impl Offset for Offset32 {
	#[inline]
	fn to_usize(&self) -> usize {
		self.0 as usize //	usize::num_from(self.0)
	}
}
impl FromData for Offset32 {
	const SIZE: usize = 4;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		u32::parse(data).map(Offset32)
	}
}
impl FromData for Option<Offset32> {
	const SIZE: usize = Offset32::SIZE;
	#[inline]
	fn parse(data: &[u8]) -> Option<Self> {
		let offset = Offset32::parse(data)?;
		if offset.0 != 0 {
			Some(Some(offset))
		} else {
			Some(None)
		}
	}
}
impl core::fmt::Debug for Offset32 {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "Offset32({})", self.0)
	}
}

/// A [`LazyArray16`]-like container, but data is accessed by offsets.
///
/// Unlike [`LazyArray16`], internal storage is not continuous.
///
/// Multiple offsets can point to the same data.
#[derive(Clone, Copy)]
pub struct LazyOffsetArray16<'a, T: FromSlice<'a>> {
	data: &'a [u8],
	// Zero offsets must be ignored, therefore we're using `Option<Offset16>`.
	offsets: LazyArray16<'a, Option<Offset16>>,
	data_type: core::marker::PhantomData<T>,
}
impl<'a, T: FromSlice<'a>> LazyOffsetArray16<'a, T> {
	/// Creates a new `LazyOffsetArray16`.
	#[allow(dead_code)]
	pub fn new(data: &'a [u8], offsets: LazyArray16<'a, Option<Offset16>>) -> Self {
		Self {
			data,
			offsets,
			data_type: core::marker::PhantomData,
		}
	}
	/// Returns a value at `index`.
	#[inline]
	pub fn get(&self, index: u16) -> Option<T> {
		let offset = self.offsets.get(index)??.to_usize();
		self.data.get(offset..).and_then(T::parse)
	}
	/// Returns array's length.
	#[inline]
	pub fn len(&self) -> u16 {
		self.offsets.len()
	}
	/// Checks if array is empty.
	#[inline]
	#[allow(dead_code)]
	pub fn is_empty(&self) -> bool {
		self.len() == 0
	}
}
impl<'a, T: FromSlice<'a> + core::fmt::Debug + Copy> core::fmt::Debug for LazyOffsetArray16<'a, T> {
	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
		f.debug_list().entries(*self).finish()
	}
}
impl<'a, T: FromSlice<'a>> FromSlice<'a> for LazyOffsetArray16<'a, T> {
	fn parse(data: &'a [u8]) -> Option<Self> {
		let mut s = Stream::new(data);
		Some(Self {
			data,
			offsets: s.parse_array16()?,
			data_type: core::marker::PhantomData,
		})
	}
}
impl<'a, T: FromSlice<'a>> IntoIterator for LazyOffsetArray16<'a, T> {
	type Item = T;
	type IntoIter = LazyOffsetArrayIter16<'a, T>;
	#[inline]
	fn into_iter(self) -> Self::IntoIter {
		LazyOffsetArrayIter16 { array: self, index: 0 }
	}
}

/// An iterator over [`LazyOffsetArray16`] values.
#[derive(Clone, Copy)]
#[allow(missing_debug_implementations)]
pub struct LazyOffsetArrayIter16<'a, T: FromSlice<'a>> {
	array: LazyOffsetArray16<'a, T>,
	index: u16,
}
impl<'a, T: FromSlice<'a>> Iterator for LazyOffsetArrayIter16<'a, T> {
	type Item = T;
	fn next(&mut self) -> Option<Self::Item> {
		if self.index < self.array.len() {
			self.index += 1;
			self.array.get(self.index - 1)
		} else {
			None
		}
	}
	#[inline]
	fn count(self) -> usize {
		usize::from(self.array.len().saturating_sub(self.index))
	}
}
